<!DOCTYPE html>
<html>
    
<head>
    <meta charset="utf-8">
    
    <title>Flutter高级玩法-贝塞尔曲线的表象认知 | ApocalypseBlog</title>
    <canvas id="header_canvas"style="position:absolute;bottom:0"></canvas> 
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

    

    

    

    

    
<link rel="stylesheet" href="/dist/build.css?v=1.14.0.css">

    <script src="/javascripts/bubble.js"></script>
    <script>
        window.isPost = true
        window.aomori = {
            
            
        }
        window.aomori_logo_typed_animated = false
        window.aomori_search_algolia = false

    </script>
<script>
    ((window.gitter = {}).chat = {}).options = {
      room: 'ApocalypseBlog/Apocalypse'
    };
  </script>
  <script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
<meta name="generator" content="Hexo 5.3.0"></head>
<!--DaoVoice服务http://blog.daovoice.io/-->

    <script>(function (i, s, o, g, r, a, m) {
        i['DaoVoiceObject'] = r;
        i[r] = i[r] ||
          function () {
            (i[r].q = i[r].q || []).push(arguments);
          };
        i[r].l = 1 * new Date();
        a = s.createElement(o);
        m = s.getElementsByTagName(o)[0];
        a.async = 1;
        a.src = g;
        a.charset = 'utf-8';
        m.parentNode.insertBefore(a, m);
      })(window, document, 'script', ('https:' === document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/b00f5052.js", 'daovoice');
      daovoice('init', {
        app_id: "b00f5052",
      });
      daovoice('update');
    </script>
  
<body>

    <div class="container">
    <header class="header">
        <div class="header-type">
            
            <div class="header-type-inner">
                
                    <a class="header-type-title" href="/">ApocalypseBlog</a>
                
    
                
            </div>
        </div>
        <div class="header-menu">
            <div class="header-menu-inner">
                
            </div>
            <div class="header-menu-social">
                
            </div>
        </div>

        <div class="header-menu-mobile">
            <div class="header-menu-mobile-inner" id="mobile-menu-open">
                <i class="icon icon-menu"></i>
            </div>
        </div>
    </header>

    <div class="header-menu-mobile-menu">
        <div class="header-menu-mobile-menu-bg"></div>
        <div class="header-menu-mobile-menu-wrap">
            <div class="header-menu-mobile-menu-inner">
                <div class="header-menu-mobile-menu-close" id="mobile-menu-close">
                    <i class="icon icon-cross"></i>
                </div>
                <div class="header-menu-mobile-menu-list">
                    
                </div>
            </div>
        </div>
    </div>

</div>

    <div class="container">
        <div class="main">
            <section class="inner">
                <section class="inner-main">
                    <div class="post">
    <article id="post-ckk6o6avx000z7knyf5c6hcbr" class="article article-type-post" itemscope
    itemprop="blogPost">

    <div class="article-inner">

        
          
        
        
        

        
        <header class="article-header">
            
  
    <h1 class="article-title" itemprop="name">
      Flutter高级玩法-贝塞尔曲线的表象认知
    </h1>
  

        </header>
        

        <div class="article-more-info article-more-info-post hairline">

            <div class="article-date">
  <time datetime="2020-06-08T01:38:19.000Z" itemprop="datePublished">2020-06-08</time>
</div>

            

            

            

        </div>

        <div class="article-entry post-inner-html hairline" itemprop="articleBody">
            <table>
<thead>
<tr>
<th>–</th>
<th>–</th>
</tr>
</thead>
<tbody><tr>
<td><img src="http://gank.io/images/647eaa29f2f14d26b7bf42d9875ffc2a" alt="img"></td>
<td><img src="http://gank.io/images/8f21d55dce424b1da3dc25bbc3f7bc47" alt="img"></td>
</tr>
<tr>
<td><img src="http://gank.io/images/79740d4480c34df9b73f102821a9ee19" alt="img"></td>
<td><img src="http://gank.io/images/9246c62fe76a447a8b115979b98cbfbc" alt="img"></td>
</tr>
</tbody></table>
<hr>
<blockquote>
<p>在玩贝塞尔之前先做点准备活动热热身。打个网格对学习贝塞尔曲线是很有帮助的。如下是以中心为原点的坐标系，<code>x向右</code>，<code>y向下</code></p>
</blockquote>
<p><img src="http://gank.io/images/0e0a93a14967429aaf7acd9372126edf" alt="img"></p>
<a id="more"></a>

<h5 id="0-1-主程序"><a href="#0-1-主程序" class="headerlink" title="0.1 : 主程序"></a>0.1 : 主程序</h5><pre><code>void main() =&gt; runApp(MyApp());

class MyApp extends StatelessWidget &#123;
  @override
  Widget build(BuildContext context) &#123;
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home:Paper());
  &#125;
&#125;</code></pre>
<hr>
<h5 id="0-2-自定义Paper组件显示画布"><a href="#0-2-自定义Paper组件显示画布" class="headerlink" title="0.2 : 自定义Paper组件显示画布"></a>0.2 : 自定义Paper组件显示画布</h5><blockquote>
<p>为了绘制的纯粹和雅观，这里把状态量去掉，并且手机横向。</p>
</blockquote>
<pre><code>
class Paper extends StatefulWidget &#123;
  @override
  _PaperState createState() =&gt; _PaperState();
&#125;

class _PaperState extends State&lt;Paper&gt; &#123;
  @override
  void initState() &#123;
    //横屏
    SystemChrome.setPreferredOrientations(
        [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
    //全屏显示
    SystemChrome.setEnabledSystemUIOverlays([]);
    super.initState();
  &#125;

  @override
  Widget build(BuildContext context) &#123;
    return  CustomPaint(
        painter: BezierPainter(),
    );
  &#125;
&#125;</code></pre>
<hr>
<h5 id="0-3-绘制网格"><a href="#0-3-绘制网格" class="headerlink" title="0.3 : 绘制网格"></a>0.3 : 绘制网格</h5><blockquote>
<p>注意: 这里永久的将画布原点移到画布的中心点，之后所以的绘制都将以中心为(0,0)点。</p>
</blockquote>
<p><img src="http://gank.io/images/d1357484cc274491b0a02a7f33354796" alt="img"></p>
<pre><code>
class BezierPainter extends CustomPainter &#123;
  Paint _gridPaint;
  Path _gridPath;

  BezierPainter() &#123;
    _gridPaint = Paint()..style=PaintingStyle.stroke;
    _gridPath = Path();
  &#125;

  @override
  void paint(Canvas canvas, Size size) &#123;
    canvas.drawColor(Colors.white, BlendMode.color);
    canvas.translate(size.width/2, size.height/2);
    _drawGrid(canvas,size);//绘制格线
    _drawAxis(canvas, size);//绘制轴线
  &#125;

  @override
  bool shouldRepaint(CustomPainter oldDelegate) =&gt; true;

  void _drawGrid(Canvas canvas, Size size) &#123;
    _gridPaint
    ..color = Colors.grey
    ..strokeWidth = 0.5;
    _gridPath = _buildGridPath(_gridPath, size);
    canvas.drawPath(_buildGridPath(_gridPath, size), _gridPaint);

    canvas.save();
    canvas.scale(1, -1); //沿x轴镜像
    canvas.drawPath(_gridPath, _gridPaint);
    canvas.restore();

    canvas.save();
    canvas.scale(-1, 1); //沿y轴镜像
    canvas.drawPath(_gridPath, _gridPaint);
    canvas.restore();

    canvas.save();
    canvas.scale(-1, -1); //沿原点镜像
    canvas.drawPath(_gridPath, _gridPaint);
    canvas.restore();

  &#125;

  void _drawAxis(Canvas canvas, Size size) &#123;
    canvas.drawPoints(PointMode.lines, [
      Offset(-size.width/2, 0) , Offset(size.width/2, 0),
      Offset( 0,-size.height/2) , Offset( 0,size.height/2),
      Offset( 0,size.height/2) , Offset( 0-7.0,size.height/2-10),
      Offset( 0,size.height/2) , Offset( 0+7.0,size.height/2-10),
      Offset(size.width/2, 0) , Offset(size.width/2-10, 7),
      Offset(size.width/2, 0) , Offset(size.width/2-10, -7),
    ], _gridPaint..color=Colors.blue..strokeWidth=1.5);
  &#125;

  Path _buildGridPath(Path path, Size size,&#123;step = 20.0&#125;) &#123;
    for (int i = 0; i &lt; size.height / 2 / step; i++) &#123;
      path.moveTo(0, step * i);
      path.relativeLineTo(size.width / 2, 0);
    &#125;
    for (int i = 0; i &lt; size.width / 2 / step; i++) &#123;
      path.moveTo( step * i,0);
      path.relativeLineTo(0,size.height / 2, );
    &#125;
    return path;
  &#125;
&#125;</code></pre>
<hr>
<h5 id="0-4、人生至美莫初见"><a href="#0-4、人生至美莫初见" class="headerlink" title="0.4、人生至美莫初见"></a>0.4、人生至美莫初见</h5><blockquote>
<p>先不看哪些花里胡哨的贝塞尔曲线的动画。让我们从实践中一点点去摸索。如此美丽的初见，为何要这么复杂？当你渐渐去认识她，了解她，熟悉她，便会明白:<code>哦，原来如此如此，这般这般...</code></p>
</blockquote>
<ul>
<li>看到<code>贝塞尔</code>三个字，也不用觉得压力太大，满打满算也就两个函数而已。</li>
</ul>
<pre><code>----&gt;[二次贝塞尔曲线]----
void quadraticBezierTo(double x1, double y1, double x2, double y2)
void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2)

----&gt;[三次贝塞尔曲线]----
void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3)
void relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3)</code></pre>
<hr>
<h4 id="一、二次贝塞尔曲线"><a href="#一、二次贝塞尔曲线" class="headerlink" title="一、二次贝塞尔曲线"></a>一、二次贝塞尔曲线</h4><blockquote>
<p>二次贝塞尔曲线需要传入四个<code>double</code>类型的值。</p>
</blockquote>
<h5 id="1-先画一笔看看"><a href="#1-先画一笔看看" class="headerlink" title="1. 先画一笔看看"></a>1. 先画一笔看看</h5><blockquote>
<p>首先新准备个画笔和路径，在构造函数里初始化。准备两个测试点<code>p1,p2</code>,<br>然后轻轻的用<code>quadraticBezierTo</code>描一笔，就出来一个曲线。</p>
</blockquote>
<p><img src="http://gank.io/images/7d58caeaa5e5476497dbcf2deaa2e7b4" alt="img"></p>
<pre><code>class BezierPainter extends CustomPainter &#123;
  // 英雄所见...
  Paint _mainPaint;
  Path _mainPath;

  BezierPainter() &#123;
    // 英雄所见...

    _mainPaint = Paint()..color=Colors.orange..style=PaintingStyle.stroke..strokeWidth=2;
    _mainPath = Path();
  &#125;
  Offset p0 =Offset(0, 0);
  Offset p1 =Offset(100, 100);
  Offset p2 =Offset( 120, -60);

    @override
  void paint(Canvas canvas, Size size) &#123;
    // 英雄所见...
    _mainPath.moveTo(p0.dx, p0.dy);
    _mainPath.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);
    canvas.drawPath(_mainPath, _mainPaint);
  &#125;</code></pre>
<hr>
<h5 id="2-为什么曲线会是这样的"><a href="#2-为什么曲线会是这样的" class="headerlink" title="2.为什么曲线会是这样的?"></a>2.为什么曲线会是这样的?</h5><blockquote>
<p>为了更好的理解贝塞尔曲线，现在我们需要绘制辅助帮我们理解。现在想将与贝塞尔曲线有关系的三个点画出来。同样，我不想弄脏画笔，所以新拿一个<code>_helpPaint</code>。在<code>_drawHelp</code>方法里进行绘制辅助线。</p>
</blockquote>
<p><img src="http://gank.io/images/6bca0a8271914cd08b8f0120617a1903" alt="img"></p>
<pre><code>class BezierPainter extends CustomPainter &#123;
  // 英雄所见...
  Paint _helpPaint;

  BezierPainter() &#123;
      // 英雄所见...
    _helpPaint = Paint()
    ..color=Colors.purple
    ..style=PaintingStyle.stroke
    ..strokeCap=StrokeCap.round;
  &#125;

 void _drawHelp(Canvas canvas) &#123;
  canvas.drawPoints(PointMode.points,[p0, p1, p1,p2], _helpPaint..strokeWidth=8);
&#125;</code></pre>
<ul>
<li>看到上图，你是不是发现的什么?如果还比较懵，再画一道辅助线</li>
</ul>
<p><img src="http://gank.io/images/1341d59b35b9425c9172d82e2bd17fa8" alt="img"></p>
<pre><code>void _drawHelp(Canvas canvas) &#123;
  canvas.drawPoints(PointMode.lines,[p0, p1, p1,p2], _helpPaint..strokeWidth=1);
  canvas.drawPoints(PointMode.points,[p0, p1, p1,p2], _helpPaint..strokeWidth=8);
&#125;</code></pre>
<hr>
<h5 id="3-来玩一下这个曲线"><a href="#3-来玩一下这个曲线" class="headerlink" title="3. 来玩一下这个曲线"></a>3. 来玩一下这个曲线</h5><blockquote>
<p>这不就是三个点嘛，要能拖拖看就好了。没问题，应你所求</p>
</blockquote>
<p><img src="http://gank.io/images/1dcb873e3caf4dfdbd0fdd430e5b1db6" alt="img"></p>
<blockquote>
<p>现在有两个要点: 【1】 如何获取触点 【2】如何通过一个触点控制三个点位</p>
</blockquote>
<hr>
<ul>
<li><code>简单讲解</code></li>
</ul>
<p>由于点位需要变化，BezierPainter<code>只承担绘制的责任</code>，这里在组件中定义<code>点位信息_pos</code>和<code>选中索引_selectIndex</code> ，通过构造函数传入BezierPainter。为了方便大家玩耍，我单独写个文件<code>play_bezier2.dart</code>里面有个PlayBezier2Page组件。</p>
<pre><code>----&gt;[_PaperState]----
class PlayBezier2Page extends StatefulWidget &#123;
  @override
  _PlayBezier2PageState createState() =&gt; _PlayBezier2PageState();
&#125;

class _PlayBezier2PageState extends State&lt;PlayBezier2Page&gt; &#123;
  List _pos = [];
  int _selectPos;

  @override
  void initState() &#123;
    //横屏
    SystemChrome.setPreferredOrientations(
        [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
    //全屏显示
    SystemChrome.setEnabledSystemUIOverlays([]);
    _initPoints();//初始化点
    super.initState();
  &#125;</code></pre>
<hr>
<ul>
<li><code>获取触点信息</code><br>通过<code>GestureDetector组件</code>可以获取触点信息，然后传给画布即可。<br>这里的思路很清晰: 在点击时需要判断点击了哪个点，抬起时取消选中点，移动时变化选中点。</li>
</ul>
<pre><code>@override
Widget build(BuildContext context) &#123;
  return GestureDetector(
    onPanDown: (detail)&#123;
     // Todo
    &#125;,
    onPanEnd: (detail)&#123;
    // Todo
    &#125;,
    onPanUpdate: (detail) &#123;
        // Todo
    &#125;,
    child: CustomPaint(
      painter: BezierPainter(pos: _pos,selectPos:selectPos),
    ),
  );
&#125;</code></pre>
<hr>
<ul>
<li><code>一个触点控制三个点位</code></li>
</ul>
<blockquote>
<p>这就有点技术含量了。需要进行<code>点域的判断</code>来确定当前点击的是哪个点。<br>比如在半径为6的区域内算作<code>命中</code>，就需要在点击时判断是否命中某个点。具体逻辑为:</p>
</blockquote>
<pre><code>///判断出是否在某点的半径为r圆范围内
bool judgeCircleArea(Offset src, Offset dst, double r) =&gt;
    (src - dst).distance &lt;= r;

void judgeSelect(Offset src, &#123;double x = 0, double y = 0&#125;) &#123;
  var p = src.translate(-x, -y);
  for (int i = 0; i &lt; _pos.length; i++) &#123;
    if (judgeCircleArea(p, _pos[i], 15)) &#123;
      selectPos = i;
    &#125;
  &#125;
&#125;
void judgeZone(Offset src, &#123;double x = 0, double y = 0&#125;) &#123;
  for (int i = 0; i &lt; _pos.length; i++) &#123;
    if (judgeCircleArea(src, _pos[i], 15)) &#123;
      selectPos = i;
      _pos[i] = src;
    &#125;
  &#125;
&#125;</code></pre>
<blockquote>
<p>前三个点需要用户点击，然后画出一段二贝曲线，之后再点击不会添加点，而是判断是否触点在期望的圆域内。这样数据的处理就完成了。根基【捷特第二定理】<code>一切的界面交互和动态视觉效果都是连续时间点状态量的变化和刷新的结合。</code>现在所有的状态量和刷新都已经实现，剩下的就是将这些量显示在界面上。</p>
</blockquote>
<pre><code>@override
Widget build(BuildContext context) &#123;
  return GestureDetector(
    onPanDown: (detail) &#123;
      if (_pos.length &lt; 3) &#123;
        _pos.add(detail.localPosition);
      &#125;
      setState(() =&gt; judgeSelect(detail.localPosition));
    &#125;,
    onPanEnd: (detail) &#123;
      setState(() =&gt; selectPos = null);
    &#125;,
    onPanUpdate: (detail) &#123;
      setState(() =&gt; judgeZone(detail.localPosition));
    &#125;,
    child: CustomPaint(
      painter: BezierPainter(pos: _pos, selectPos: selectPos),
    ),
  );
&#125;</code></pre>
<hr>
<ul>
<li><code>绘制</code></li>
</ul>
<blockquote>
<p>网格和辅助的和上面逻辑基本一致，详见源码，这里就不贴了。当点数小于三个时，仅绘制触点，否则绘制曲线和辅助线。</p>
</blockquote>
<p><img src="http://gank.io/images/13ee839cb7f343be8da4d5e378f0b4c4" alt="img"></p>
<blockquote>
<p>有一点需要注意: 我们的点位是相对于屏幕左上角的，需要平移到画布中心</p>
</blockquote>
<pre><code>class BezierPainter extends CustomPainter &#123;

  Paint _mainPaint;
  Path _mainPath;
  int selectPos;

  List pos;

  BezierPainter(&#123;this.pos, this.selectPos&#125;) &#123;
    _mainPaint = Paint()
      ..color = Colors.orange
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2;
    _mainPath = Path();
  &#125;

  @override
  void paint(Canvas canvas, Size size) &#123;
    pos = pos.map((e)=&gt;e.translate(-size.width / 2, -size.height / 2)).toList();
    canvas.drawColor(Colors.white, BlendMode.color);
    canvas.translate(size.width / 2, size.height / 2);
    _drawGrid(canvas, size); //绘制格线
    _drawAxis(canvas, size); //绘制轴线

    if(pos.length&lt;3)&#123;
      canvas.drawPoints(PointMode.points, pos, _helpPaint..strokeWidth = 8);
    &#125;else&#123;
      _mainPath.moveTo(pos[0].dx, pos[0].dy);
      _mainPath.quadraticBezierTo(pos[1].dx, pos[1].dy, pos[2].dx, pos[2].dy);
      canvas.drawPath(_mainPath, _mainPaint);
      _drawHelp(canvas);
      _drawSelectPos(canvas);
    &#125;
  &#125;

  // 英雄所见...
  void _drawSelectPos(Canvas canvas) &#123;
    if (selectPos == null) return;
    canvas.drawCircle(
        pos[selectPos],
        10,
        _helpPaint
          ..color = Colors.green
          ..strokeWidth = 2);
  &#125;
&#125;</code></pre>
<blockquote>
<p>通过前面的介绍，一段二次的贝塞尔曲线有三个点决定，<code>起点</code>、<code>控制点</code>、<code>终点</code><br>关于起点，默认是（0，0），你也在绘制之前moveTo设置起点，当绘制连续的贝塞尔曲线，下一段曲线的起点就是上一段的终点。所以二次贝塞尔曲线至关重要的是两个点: <code>也就是入参中的控制点和终点</code>。</p>
</blockquote>
<hr>
<h4 id="二、三次贝塞尔曲线"><a href="#二、三次贝塞尔曲线" class="headerlink" title="二、三次贝塞尔曲线"></a>二、三次贝塞尔曲线</h4><blockquote>
<p>前面的二次贝塞尔实现了，那现在来看三次的<code>cubicTo</code>。需要六个参数，也就是三个点。<br>我们可以使用之前的代码，很快捷的生成如下效果。源代码在<code>play_bezier3.dart</code></p>
</blockquote>
<p><img src="http://gank.io/images/d5e7761fc3cc46ce9a6a6ffb33498e7f" alt="img"></p>
<hr>
<h5 id="1-实现三贝单线操作"><a href="#1-实现三贝单线操作" class="headerlink" title="1.实现三贝单线操作"></a>1.实现三贝单线操作</h5><blockquote>
<p>前面点集在<code>_pos</code>中维护,现在需要四个点，so easy</p>
</blockquote>
<ul>
<li><code>点击时将限制数改为4个</code></li>
</ul>
<pre><code>----&gt;[_PlayBezier3PageState]----
onPanDown: (detail) &#123;
  if (_pos.length &lt; 4) &#123;
    _pos.add(detail.localPosition);
  &#125;
  setState(() =&gt; judgeSelect(detail.localPosition));
&#125;</code></pre>
<hr>
<ul>
<li><code>绘制将限制数改为4个</code></li>
</ul>
<pre><code>if(pos.length&lt;4)&#123;
  canvas.drawPoints(PointMode.points, pos, _helpPaint..strokeWidth = 8);
&#125;else&#123;
  _mainPath.moveTo(pos[0].dx, pos[0].dy);
  _mainPath.cubicTo(pos[1].dx, pos[1].dy, pos[2].dx, pos[2].dy, pos[3].dx, pos[3].dy);
  canvas.drawPath(_mainPath, _mainPaint);
  _drawHelp(canvas);
  _drawSelectPos(canvas);
&#125;</code></pre>
<p><code>That is all</code> ,这就是分工明确的好处，变化时只变需变化待变化的，整体的流程和思路是恒定的。</p>
<hr>
<h5 id="2-三贝中的拟圆"><a href="#2-三贝中的拟圆" class="headerlink" title="2.三贝中的拟圆"></a>2.三贝中的拟圆</h5><blockquote>
<p>三贝很厉害，可以说无所不能。只有你想不到，没有她做不到<br>Ps中的钢笔路径就是多段的三贝曲线。所以还是很有玩头的。</p>
</blockquote>
<p><img src="http://gank.io/images/ac322126fb5d41fa84c8732598bf66cd" alt="img"></p>
<p>--</p>
<ul>
<li><code>绘制拟圆</code></li>
</ul>
<blockquote>
<p>下面的图看着像个圆，但其实是四段三贝拟合而成的。目前我们的代码中最在意的就是点位数据。所以关键就是寻找点。本小节源码在:<code>circle_bezier.dart</code>中</p>
</blockquote>
<p><img src="http://gank.io/images/0128149f1cca4670960eb5ef47625f09" alt="img"></p>
<ul>
<li><code>第一段-左下</code></li>
</ul>
<blockquote>
<p>这里直接给出点，至于<code>0.551915024494</code>是什么，后面有机会会带你一起推导。有兴趣的话，你也可以自己查一查资料。和之前一样，核心的绘制就是那么一句。</p>
</blockquote>
<p><img src="http://gank.io/images/35063e85917b4a0781194b67e52866c5" alt="img"></p>
<pre><code>----&gt;[CircleBezierPage]----
class CircleBezierPage extends StatefulWidget &#123;
  @override
  _CircleBezierPageState createState() =&gt; _CircleBezierPageState();
&#125;

class _CircleBezierPageState extends State &#123;
  List _pos = [];
  int selectPos;

  //单位圆(即半径为1)控制线长
  final rate = 0.551915024494;
  double _radius=150;
  @override
  void initState() &#123;
    //横屏
    SystemChrome.setPreferredOrientations(
        [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]);
    //全屏显示
    SystemChrome.setEnabledSystemUIOverlays([]);
    _initPoints();
    super.initState();
  &#125;

  void _initPoints() &#123;
    _pos = List();
    //第一段线
    _pos.add(Offset(0,rate)*_radius);
    _pos.add(Offset(1 - rate, 1)*_radius);
    _pos.add(Offset(1, 1)*_radius);
  &#125;

  @override
  Widget build(BuildContext context) &#123;
    return CustomPaint(
        painter: BezierPainter(pos: _pos, selectPos: selectPos),
        ),
    );
  &#125;

----&gt;[BezierPainter#paint]----
_mainPath.moveTo(0, 0);
for (int i = 0; i &lt; pos.length / 3; i++) &#123;
  _mainPath.cubicTo(
       pos[3*i+0].dx,  pos[3*i+0].dy,
       pos[3*i+1].dx, pos[3*i+1].dy,
       pos[3*i+2].dx,  pos[3*i+2].dy);
&#125;</code></pre>
<hr>
<ul>
<li><code>其他三段</code></li>
</ul>
<blockquote>
<p>初始点时，将这12点放入列表。然后将赋值的点线绘制出来。</p>
</blockquote>
<p><img src="http://gank.io/images/c6fb91536d634fec8d47b872e1436d29" alt="img"></p>
<pre><code>----&gt;[CircleBezierPage#_initPoints]----
void _initPoints() &#123;
  _pos = List();
  //第一段线
  _pos.add(Offset(0,rate)*_radius);
  _pos.add(Offset(1 - rate, 1)*_radius);
  _pos.add(Offset(1, 1)*_radius);
  //第二段线
  _pos.add(Offset(1 + rate, 1)*_radius);
  _pos.add(Offset(2, rate)*_radius);
  _pos.add(Offset(2, 0)*_radius);
  //第三段线
  _pos.add(Offset(2, -rate)*_radius);
  _pos.add(Offset(1 + rate, -1)*_radius);
  _pos.add(Offset(1, -1)*_radius);
  //第四段线
  _pos.add(Offset(1 - rate, -1)*_radius);
  _pos.add(Offset(0, -rate)*_radius);
  _pos.add(Offset(0, 0));
&#125;

----&gt;[BezierPainter#_drawHelp]----
void _drawHelp(Canvas canvas) &#123;
  _helpPaint..strokeWidth = 1;
  canvas.drawLine(pos[0], pos[11],_helpPaint);
  canvas.drawLine(pos[1], pos[2],_helpPaint);
  canvas.drawLine(pos[2], pos[3],_helpPaint);
  canvas.drawLine(pos[4], pos[5],_helpPaint);
  canvas.drawLine(pos[5], pos[6],_helpPaint);
  canvas.drawLine(pos[7], pos[8],_helpPaint);
  canvas.drawLine(pos[8], pos[9],_helpPaint);
  canvas.drawLine(pos[10], pos[11],_helpPaint);
  canvas.drawLine(pos[11], pos[0],_helpPaint);
  canvas.drawPoints(PointMode.points, pos, _helpPaint..strokeWidth = 8);
&#125;</code></pre>
<hr>
<h5 id="3-三贝中的拟圆的操作"><a href="#3-三贝中的拟圆的操作" class="headerlink" title="3.三贝中的拟圆的操作"></a>3.三贝中的拟圆的操作</h5><blockquote>
<p>看这控制柄，满满的拖动欲望，来实现一下吧<br>有了之前的铺垫，下面的代码应该很容易接受吧。</p>
</blockquote>
<p><img src="http://gank.io/images/9900a9df8a2b45d39f1d2569ce8164b3" alt="img"></p>
<pre><code>@override
Widget build(BuildContext context) &#123;
  var x = MediaQuery.of(context).size.width/2;
  var y = MediaQuery.of(context).size.height/2;
  return GestureDetector(
    onPanDown: (detail) &#123;
      setState(() =&gt; judgeSelect(detail.localPosition,x: x,y: y));
    &#125;,
    onPanEnd: (detail) &#123;
      setState(() =&gt; selectPos = null);
    &#125;,
    onPanUpdate: (detail) &#123;
      setState(() =&gt; judgeZone(detail.localPosition,x: x,y: y));
    &#125;,
    child: CustomPaint(
      painter: BezierPainter(pos: _pos, selectPos: selectPos),
    ),
  );
&#125;
///判断出是否在某点的半径为r圆范围内
bool judgeCircleArea(Offset src, Offset dst, double r) =&gt;
    (src - dst).distance &lt;= r;
void judgeSelect(Offset src, &#123;double x = 0, double y = 0&#125;) &#123;
  print(src);
  var p = src.translate(-x, -y);
  print(p);
  for (int i = 0; i &lt; _pos.length; i++) &#123;
    if (judgeCircleArea(p, _pos[i], 15)) &#123;
      selectPos = i;
    &#125;
  &#125;
&#125;
void judgeZone(Offset src, &#123;double x = 0, double y = 0&#125;) &#123;
  var p = src.translate(-x, -y);
  for (int i = 0; i &lt; _pos.length; i++) &#123;
    if (judgeCircleArea(p, _pos[i], 15)) &#123;
      selectPos = i;
      _pos[i] = p;
    &#125;
  &#125;
&#125;</code></pre>
<hr>
<h4 id="三、贝塞尔曲线与路径操作"><a href="#三、贝塞尔曲线与路径操作" class="headerlink" title="三、贝塞尔曲线与路径操作"></a>三、贝塞尔曲线与路径操作</h4><blockquote>
<p>也许你觉得贝塞尔曲线也就那样。那么你忽略了一个很重要的东西。<br><code>贝塞尔曲线</code>是一条路径。路径是个什么东西，之前写了一篇关于路径使用的冰山一角<br><a target="_blank" rel="noopener" href="https://juejin.im/post/5e6196066fb9a07c8b5bbdf5">【Flutter高级玩法-shape】Path在手，天下我有</a></p>
</blockquote>
<blockquote>
<p>现在再准备一条路径，看看路径间的如何操作</p>
</blockquote>
<p><img src="http://gank.io/images/08fc785cae1648b3a75980aa9710a746" alt="img"></p>
<pre><code>class BezierPainter extends CustomPainter &#123;

  Path _clipPath;
  //英雄所见...

  BezierPainter(&#123;this.pos, this.selectPos&#125;) &#123;
    _clipPath=Path();
  //英雄所见...

 @override
void paint(Canvas canvas, Size size) &#123;
   //英雄所见...
  _clipPath.addOval(Rect.fromCenter(center: Offset(0, 0),width: 100,height: 100));
  canvas.drawPath(_clipPath, _mainPaint);
//英雄所见...
&#125;</code></pre>
<hr>
<h5 id="1-路径的相减-PathOperation-difference"><a href="#1-路径的相减-PathOperation-difference" class="headerlink" title="1.路径的相减: PathOperation.difference"></a>1.路径的相减: <code>PathOperation.difference</code></h5><p><img src="http://gank.io/images/5cf646966a164ddf935441b3444782c3" alt="img"></p>
<pre><code>  @override
  void paint(Canvas canvas, Size size) &#123;
    //英雄所见...
    var drawPath = Path.combine(PathOperation.difference, _mainPath, _clipPath);
    canvas.drawPath(drawPath, _mainPaint);</code></pre>
<hr>
<h5 id="2-路径的相加-PathOperation-union"><a href="#2-路径的相加-PathOperation-union" class="headerlink" title="2.路径的相加: PathOperation.union"></a>2.路径的相加: <code>PathOperation.union</code></h5><p><img src="http://gank.io/images/44eaa6e604b046c99c7dcbea757327b6" alt="img"></p>
<pre><code>  @override
  void paint(Canvas canvas, Size size) &#123;
    //英雄所见...
    var drawPath = Path.combine(PathOperation.union, _mainPath, _clipPath);
    canvas.drawPath(drawPath, _mainPaint);</code></pre>
<hr>
<h5 id="3-路径的反减-PathOperation-reverseDifference"><a href="#3-路径的反减-PathOperation-reverseDifference" class="headerlink" title="3.路径的反减: PathOperation.reverseDifference"></a>3.路径的反减: <code>PathOperation.reverseDifference</code></h5><p><img src="http://gank.io/images/8e8a1ccb1bc9463786f2c806a10d9b3a" alt="img"></p>
<pre><code>  @override
  void paint(Canvas canvas, Size size) &#123;
    //英雄所见...
    var drawPath = Path.combine(PathOperation.reverseDifference, _mainPath, _clipPath);
    canvas.drawPath(drawPath, _mainPaint);
</code></pre>
<hr>
<h5 id="4-路径的交集-PathOperation-intersect"><a href="#4-路径的交集-PathOperation-intersect" class="headerlink" title="4.路径的交集: PathOperation.intersect"></a>4.路径的交集: <code>PathOperation.intersect</code></h5><p><img src="http://gank.io/images/ed41d9840a95400fb103f5d5f08d4e1a" alt="img"></p>
<pre><code>  @override
  void paint(Canvas canvas, Size size) &#123;
    //英雄所见...
    var drawPath = Path.combine(PathOperation.intersect, _mainPath, _clipPath);
    canvas.drawPath(drawPath, _mainPaint);
</code></pre>
<hr>
<h5 id="5-路径的反交集-PathOperation-xor"><a href="#5-路径的反交集-PathOperation-xor" class="headerlink" title="5.路径的反交集: PathOperation.xor"></a>5.路径的反交集: <code>PathOperation.xor</code></h5><blockquote>
<p>当然路径并非是线条,也可以进行填色。</p>
</blockquote>
<p><img src="http://gank.io/images/f198da981a224c78bb70e1f59684da75" alt="img"></p>
<pre><code>  @override
  void paint(Canvas canvas, Size size) &#123;
    //英雄所见...
    var drawPath = Path.combine(PathOperation.xor, _mainPath, _clipPath);
    canvas.drawPath(drawPath, _mainPaint..style=PaintingStyle.fill);
</code></pre>
<blockquote>
<p>OK,本篇到这里就告一段落，下一篇会找几个实际的用途，来看看贝塞尔曲线的妙用。 敬请期待。</p>
</blockquote>

        </div>

    </div>

    

    

    

    

    

    
<nav class="article-nav">
  
    <a href="/2020/06/09/Handler-%E6%9C%BA%E5%88%B6/" id="article-nav-newer" class="article-nav-link-wrap">
      <div class="article-nav-caption">下一篇</div>
      <div class="article-nav-title">
        
          Handler 机制
        
      </div>
    </a>
  
  
    <a href="/2020/06/06/hexo%E6%92%B0%E5%86%99%E6%96%87%E7%AB%A0/" id="article-nav-older" class="article-nav-link-wrap">
      <div class="article-nav-caption">上一篇</div>
      <div class="article-nav-title">hexo撰写文章</div>
    </a>
  
</nav>


    <section class="share">
        <div class="share-title">分享</div>
        <a class="share-item" target="_blank"
            href="https://twitter.com/share?text=Flutter高级玩法-贝塞尔曲线的表象认知 - ApocalypseBlog&url=http://example.com/2020/06/08/%E3%80%90Flutter%E9%AB%98%E7%BA%A7%E7%8E%A9%E6%B3%95%E3%80%91%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF%E7%9A%84%E8%A1%A8%E8%B1%A1%E8%AE%A4%E7%9F%A5/">
            <box-icon type='logo' name='twitter'></box-icon>
        </a>
        <a class="share-item" target="_blank"
            href="https://www.facebook.com/sharer.php?title=Flutter高级玩法-贝塞尔曲线的表象认知 - ApocalypseBlog&u=http://example.com/2020/06/08/%E3%80%90Flutter%E9%AB%98%E7%BA%A7%E7%8E%A9%E6%B3%95%E3%80%91%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF%E7%9A%84%E8%A1%A8%E8%B1%A1%E8%AE%A4%E7%9F%A5/">
            <box-icon name='facebook-square' type='logo' ></box-icon>
        </a>
        <!-- <a class="share-item" target="_blank"
            href="https://service.weibo.com/share/share.php?title=Flutter高级玩法-贝塞尔曲线的表象认知 - ApocalypseBlog&url=http://example.com/2020/06/08/%E3%80%90Flutter%E9%AB%98%E7%BA%A7%E7%8E%A9%E6%B3%95%E3%80%91%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF%E7%9A%84%E8%A1%A8%E8%B1%A1%E8%AE%A4%E7%9F%A5/&pic=">
            <div class="n-icon n-icon-weibo"></div>
        </a> -->
    </section>

</article>









</div>
                </section>
            </section>

             
            <aside class="sidebar">
            
                
            </aside>
        </div>
    </div>

    <footer class="footer">
    <div class="footer-wave">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="#3c4859" fill-opacity="1" d="M0,160L60,181.3C120,203,240,245,360,240C480,235,600,181,720,186.7C840,192,960,256,1080,261.3C1200,267,1320,213,1380,186.7L1440,160L1440,320L1380,320C1320,320,1200,320,1080,320C960,320,840,320,720,320C600,320,480,320,360,320C240,320,120,320,60,320L0,320Z"></path></svg>
    </div>

    <div class="footer-wrap">
        <div class="footer-inner"> 
            ApocalypseBlog &copy; 2021<br>
            Powered By Hexo · Theme By <a href="https://github.com/lh1me/hexo-theme-aomori" target="_blank">Aomori</a>
        </div>
    </div>

</footer>




<script src="/dist/build.js?1.14.0.js"></script>


<script src="/dist/custom.js?1.14.0.js"></script>









</body>

</html>